Skocz do zawartości
  • 👋 Witaj na MPCForum!

    Przeglądasz forum jako gość, co oznacza, że wiele świetnych funkcji jest jeszcze przed Tobą! 😎

    • Pełny dostęp do działów i ukrytych treści
    • Możliwość pisania i odpowiadania w tematach
    • System prywatnych wiadomości
    • Zbieranie reputacji i rozwijanie swojego profilu
    • Członkostwo w jednej z największych społeczności graczy

    👉 Dołączenie zajmie Ci mniej niż minutę – a zyskasz znacznie więcej!

    Zarejestruj się teraz

klasy co i jak


Rekomendowane odpowiedzi

Opublikowano

Witam

 

Możecie mi powiedzieć jaki jest sens stosowania klas w C++, konstruktorów i konstruktorów bo kolesiana ucząca mnie programowania nie potrafi odpowiedzieć na to pytanie..

 

Byłbym wdzięczny gdybyście byli podać jakieś konkretne przykłady kodu :)

Obama wie, co robisz!!!
131894.jpg                                                                                                                                                    4906167742.png

                                                                                                                                                                                                                                                                                      LTE Play Opole

Opublikowano

Wiesz jak nie chcesz używać klas to po prostu pisz w C. Same Programowanie Obiektowe jest aktualnym standardem, a jest nim przez przydatność i zmniejszanie rozmiaru kodu. Konstruktory to "metoda" która jest wywoływana podczas powoływania do życia obiekt(tworzenia instancji) służy głównie do inicjowania pól.

Opublikowano

Chcę używać klas tylko sposób w jaki to przedstawiła nauczycielka sprawił, że postrzegam to w taki a nie inny sposób. Chciałbym zobaczyć fragment kodu gdzie jest zastosowana klasa razem z konstruktorami i dekonstruktorami żeby moc go przeanalizować, może wtedy doszedłbym po co to jest ;)

Obama wie, co robisz!!!
131894.jpg                                                                                                                                                    4906167742.png

                                                                                                                                                                                                                                                                                      LTE Play Opole

Opublikowano

class hero

{

 private:

   int hp=100;

   int mp=100;

   int x;

   int y;

   char* Name;

 

public:

 hero(int x,int y,char* name)

 {

   this->x=x;

   this->y=y;

   this->Name=name;

 }

 

 ~hero()

 {

   printf("Bohater zdechł");

 }

}

 

 

void Main()

{

  hero* boh = new hero(0,0,"konserwa");

  hero* boh2 = new hero(1,1,"Rex");

 

  delete boh;

  delete boh2;

}

 

zastanów się jakbyś ty zrobił 2 bohaterów programując strukturalnie?

Opublikowano

Widzę, że stosujesz wskaźniki i polecenie this-> oraz new z którymi nie miałem wcześniej do czynienia więc przyznam się że mało co ogarniam z tego przykładu

Obama wie, co robisz!!!
131894.jpg                                                                                                                                                    4906167742.png

                                                                                                                                                                                                                                                                                      LTE Play Opole

Opublikowano

Powód jest prosty. WYGODA.

Nawet ciężko jest podać dobry przykład, bo większość kodów napisanych obiektowo jest ciężko przedstawić w inny sposób.

 

bardzo prosty przykład

obiektowo

class A
{
public:
    int x;
    float y;
    A(int _x, float _y) : x(_x), y(_y){}
    virtual void siema() = 0;
};
class B : public A
{
public:
    B(int _x, float _y) : A(_x, _y){}
    void siema()
    {
        cout << x << ' ' << y;
    }
};
class C : public B
{
public:
    C(int _x, float _y) : B(_x, _y){}
    void siema()
    {
        cout << y << ' ' << x << "No jest inaczej, nie?";
    }
};
int main()
{
    A* bc[2] = {new B(1,2.3), new C(33,2.7)};
    for(int i = 0; i<2;++i)
    {
        bc[i]->siema();
        cout << '\n';
    }
    delete[] bc;
    return 0;
}

nie obiektowo

void siema1(int x, float y)
{
    cout << x << ' ' << y;
}
void siema2(int x, float y)
{
    cout << y << ' ' << x << "No jest inaczej, nie?";
}
void (*siema[2])(int, float) = {siema1, siema2};
int main()
{
    int pierwsze[2] = {1, 33};
    float drugie[2] = {2.3, 2.7};
    for(int i = 0; i<2;++i)
    {
        siema[i](pierwsze[i], drugie[i]);
        cout << '\n';
    }
    return 0;
}

Teraz sobie wyobraź, że pól w klasie jest 30, poziomów dziedziczenia 10, a metod 15.

 

Zresztą sam zrozumiesz jak przyjdzie ci coś więcej napisać.

 

 

@kolepka

nie powinno się tego typu funkcjonalności pisać w destruktorze

Opublikowano

Wiedziałem, że ty to napiszesz jakąś głupotę. Jakiej funkcjonalności? To że postać zmarła? To że obiekt jest już martwy? Jest sens i powinno się to robić jak używasz wielowątkowości. Ale masz dopiero 16lat może na studiach sie dowiesz dlaczego.

Opublikowano

Wiedziałem, że ty to napiszesz jakąś głupotę. Jakiej funkcjonalności? To że postać zmarła? To że obiekt jest już martwy? Jest sens i powinno się to robić jak używasz wielowątkowości. Ale masz dopiero 16lat może na studiach sie dowiesz dlaczego.

 

A teraz pomyśl, że chcesz wyłączyć grę. I co? Napisze ci, że postać zmarła WTF?!

 

 

 

 

Do tego powinna być osobna metoda.

 

@edit.

inny przykład

Masz 2 różne światy, które nie są jednocześnie wczytane. W jednym z nich jest jakiś gość, która ma wiadomość w destruktorze. Zmieniasz świat, dealokujesz pamięć tamtego gościa i kolejny WTF.

 

Kiedyś też tak pisałem i się nieźle na tym przejechałem.

 

@edit

jeszcze inaczej

masz destruktor klasy A, który coś wypisuje

teraz

A a;

...

a = A(costam);

miłego debugowania

Opublikowano

@piotrekkkk

Współczuję nauczyciela. Jednak zastanawia mnie: co dokładnie rozumiesz przez "nie potrafi odpowiedzieć na pytanie"? Jakiej odpowiedzi udziela? 

 

Rozbiję problem na dwa pytania.

 

 

- Dlaczego stosować klasy? 

 

To jest niesamowicie trudne pytanie i absolutnie nie mam kompetencji, by na nie wyczerpująco odpowiedzieć. Wszystko w tym punkcie jest znacznym uproszczeniem, które może wręcz ocierać się o błędność i fałsz. 

 

Naprawdę krótka odpowiedź brzmi: A dlaczego nie?

 

Czy zastnawiałeś się kiedś jak wygląda program, który drukuje tekst na ekranie w Assemblerze, najprymitywniejszym języku, najbardziej zbliżonym do kodu maszynowego? 





mov ax, .data
mov ds, ax
mov ax, stosik
mov ss, ax
mov dx, tekst
mov ah, 9
int 21h
mov ax, 4C00h
int 21h

Czy potrafisz wyobrazić sobie, jak w takim języku wyglądałby program odpowiedzialny za zarządzanie książkami w bibliotece? Ja nie chcę wiedzieć. 

 

Najważniejsza cecha Assemblera, widoczna nawet na tak małym przykładzie to: 

operowanie na zerowym poziomie abstrakcji

Pracujemy bezpośrednio na fizycznym sprzęcie ["ds, ax, ss, dx, ah" - to symbole rejestrów procesora] używając prostych i jednoznacznych instrukcji ["mov" - przeniesienie wartości z X do Y, "int" - instrukcja zatrzymania z kodem X ]

 

Wraz z rozwojem informatyki, istniała silna tendencja do korzystania z mechanizmów oferujących wyższy poziom abstrakcji.

 

Stopniowo wprowadziliśmy koncepcje:

- zmiennej - fragmentu pamięci określonego mnemoniczną (łatwą do zapamiętania) nazwą, wybraną przez programistę

funkcji - reprezentujące logikę aplikacji. Implementujące algorytm

 

Programy zostały podzielone na DANE, reprezentowane przez zmienne, oraz FUNKCJE, wykonujące algorytm, który zmieniał DANE.

 

Jednak nie wszystkie funkcje operują na wszystkich zmiennych. 

Wiele funkcji, podczas swojego wykonywania, musi tymczasowo zająć obszar pamięci i stworzyć kilka zmiennych tylko po to, by dokonać obliczeń. 

 

Zmienna PI = 3.15 ( informatyka była wtedy bardzo w powijakach ) powinna być dostępna dla każdego zainteresowanego, ponieważ jest bardzo ważna, jednak zmienna i, odpowiedzialna za liczbę wykonań pętli... jest istotna tylko w kontekście jednej pętli. 

 

Pojawiła się potrzeba, by w pewien sposób rozdzielić pewne zmienne od innych. Stworzono pojęcie zakresu, czyli czasu życia zmiennej - obszaru aplikacji, w którym zmienna jest wykorzystywana a później już - niepotrzebna. 

 

Mało tego - pewne funkcje nie powinny w ogóle wiedzieć o istnieniu innych funkcji. Funkcja licząca logarytm naturalny powinna być ogólnie dostępna, natomiast funkcja zwracająca liczbę książek napisanych przez najczęściej wypożycznego autora... już nie. 

 

Zresztą... Zmienna PI, logarytm naturalny... czy ja naprawdę tego potrzebuję przy tworzeniu aplikacji do zarządzania książkami w bibliotece? 

Czy nie mogę wybrać tylko części funkcji, które są dla mnie faktycznie użyteczne do wykonania danego zadania? Czy nie mogę zgrupować funkcji? 

Jeżeli pracuję nad obsługą klienta, czy nie mogę w ścisły sposób wydzielić fragmentu aplikacji odpowiedzialnego za faktyczną obsługę klienta; natomiast gdy pracuję nad archiwizacją książek - wydzielić fragmentu odpowiedzialnego za kompresję danych oraz indeksowanie

 

Przecież NIGDY nie kompresuję klienta i nigdy nie wydaję reszty mojej książce... 

 

 

 

- - - - - - - - - - - - - - - - -

 

 

 

Wróćmy jednak do zmiennych i tego, co dokładnie one reprezentują

 

Początkowo zmienne nie miały typu. Przedstawiały po prostu nazwany fragment pamięci, do którego można było się odwołać w dowolnym miejscu. Jak już zarysowaliśmy, ta dowolność w odwoływaniu sama w sobie stanowiła problem. 

 

Jednak nie wszystkie zmienne reprezentowały to samo. Inaczej chcielibyśmy interpretować wartość całkowitą, inaczej zmiennoprzecinkową, jeszcze inaczej łańcuch znaków. 

 

I skoro wprowadziliśmy różne typy dla słowaliczby naturalnejliczby ujemnej, czy ułamka... Dlaczego nie dla samochodu? Dlaczego nie dla książki

Dlaczego niemielibśmy mieć typu, który reprezentowałby samolot, albo węża? Który zawsze i wszędzie, w każdym miejscu aplikacji, traktowany byłby zawsze za samolot ( albo węża ). 

 

Możliwość wprowadzenia typu implementowego przez programistę, który gwarantowałby że w każdym miejscu wykonania programu ta część pamięci będzie traktowana jako samolot.... to jest cholernie potężna możliwość. 

 

Mało tego, każdy typ może składać się z wielu innych typów. Samolot złożony jest skomplikowaną maszyną i chciałbym go rozpatrywać na wiele sposobów: 

- jako całość, gdy faktycznie pełni swoją funkcję jako całość, to jest transportuje ludzi z punktu A do B

- jako zbiór poszczególnych części, podczas konserwacji i drobnych napraw

- jako maszynkę do pieniędzy, czyli iloczyn liczby pasażerów przez koszt biletu, minus koszty załogi i obsługi; w ujęciu rentowności kapitałowej przedsięwzięć długoterminowych niskiego i średniego ryzyka... 

 

I niech mnie kule biją, jeśli od czasu do czasu nie będę chciał go porównać z Limuzyną. 

- aby stwierdzić, czy inwestycja w limuzynę jest bardziej rentowna i generuje większe zyski 

- aby sprawdzić, czym bardziej komfortowo dojadę do Nowego Jorku

 

A od czasu do czasu, ponieważ jestem początkującym informatykiem, będę próbował się przekonać, czy mój samolot ma taki sam kolor śrubek jak moja wiewiórka. Dlaczego? 

PONIEWAŻ MOGĘ. Ponieważ wysoka abstrakcja zapewnia mi możliwości myślenia o - i operowania na - obiektach informatycznych w taki sposób, w jaki od zawsze operowałem na obiektach świata rzeczywistego. 

 

 

 

- - - - - - - - - - - - - - - - -

 

 

 

Te dwie drogi, te dwie potrzeby, doprowadziły do masowego rozwoju i popularyzacji Programowania Orientowanego Obiektowo (OOP - Object Oriented Programming)

 

Do dwóch najważniejszych cech tego Paradygmatu należą: 

- ENKAPSULACJA:

        - Możliwość ścisłego zdefiniowania która część programu jest widoczna dla wszystkich, która część dla wybranych, a która część tylko prywatnie.

        - Możliwość zgrupowania funkcji ze względu na typ danych, z którymi te funkcje pracują

- OPEROWANIE NA WYŻSZYM POZIOMIE ABSTRAKCJI:

        - Możliwość przedstawienia dowolnego fenomenu za pomocą abstrakcyjnego typu danych.

        - Możliwość przedstawienia programu i rozwiązywanie skomplikowanego problemu jako zbioru elementów wzajemnie oddziałujących i komunikujących się między sobą. 

 

Wbrew pozorom, nie jest to dyktowane tym, że jest tak łatwiej. W istocie, rozwiązanie pewnych prostych problemów, w oparciu o OOP, może okazać się trudniejsze. Kod @SOPELKA chyba najlepszym przykładem. "Wyobraź sobie..."

"Łatwość" i "trudność" w wykonaniu zadania stosując dane podejście jest również związana z osobistymi preferencjami i doświadczeniami Programisty. Ciężko tutaj o jakąkolwiek obiektywną ekspertyzę. 

Natomiast dwa najważniejsze powody - w zasadzie kwintesencja OOP - to właśnie Abstrakcja i Enkapsulacja. Istnieją również dwa pochodne pojęcia związane z OOP - Polimorfizm i Dziedziczenie - jednak to już do własnego researchu. 

 

Bardzo ZŁY zbiór paradygmatów ( znaczy zbiór dobry bo kompletny, ale opis i zgrupowanie tragiczne; używać tylko jako listę terminów do dalszego googlowania): http://pl.wikipedia.org/wiki/Paradygmat_programowania

 

 

Wiedząc już o OOP odrobinę więcej, niż gdy zaczęliśmy, być może wolno nam pokusić się o odrobinę dłuższą, lecz wciąż krótką odpowiedź: 

OOP pozwala stworzyć realistyczny model skomplikowanego problemu i dokonać jego podziału na części, których szczególowa implementacja jest niezależna od siebie, co umożliwia symultaniczną pracę nad jednym problemem dużej liczbie Programistów. Niekoniecznie szybciej ( istnieje dość wartka debata w półświatku dotycząca szybkości implementacji w oparciu o poszczególne paradgmaty ) w kategoriach całkowitej liczby roboczogodzin, ale... współbieżnie. 

 

 

 

 

 

 

 

- Czym jest konstruktor i dlaczego go stosować? 

 

 

 

Konstruktor dla programisty: Jest to funkcja wywoływana za każdym razem, gdy tworzony jest obiekt jakiejś klasy.

Każda nie-pusta klasa posiada swój konstruktor, nawet jeśli go specjalnie nie zadeklarujesz. Dzieje się tak ponieważ, dla kompilatora: konstruktor to funkcja, odpowiedzialna za zarezerwowanie pamięci przeznaczonej dla danej klasy. 

W momencie gdy stworzony zostanie obiekt klasy





class mojaLiczba { int wartość; }

Fizycznie w pamięci musi zostać zarezerowanych 8 bitów ( lub inna liczba właściwa dla typu int, w zależności od architektury procesora i implementacji danego kompilatora ), w których może być przechowywana zmienna int wartość.

Natomiast, jeśli programista sobie zażyczy, konstruktor klasy może robić więcej niż tylko rezerować pamieć. Może - na przykład - ją inicjalizować. W mniej lub bardziej fikuśny sposób. 





class mojaLiczbaZInternetu
{
  int wartość; 
 
  mojaLiczbaZInternetu() { wartość = PlikZDysku.WczytajWartość("baza_danych.txt"); /* to jest pseudokod; to nie będzie "samo z siebie" działać */ } 
}

Obowiązuje przy tym zasada, iż - w klasycznym przypadku - to kompilator odpowiedzialny jest za rezerwowanie pamięci i dzieje się to zawsze przed wykonaniem konstruktora zadeklarowanego przez programistę. Jest to pewne uproszczenie, gdyż istnieją trzy sposoby zadeklarowania obiektu - i w zależności od tego, który wybierze programista, kompilator zarezerwuje pamięć w inny ( mniej lub bardziej kontrolowany ) sposób. 

 

 

 

 

 

Appendix ( nie czytaj tego, jeśli masz problem z przyswojeniem treści powyżej tej linii ) 

 

 

Trzy sposoby zadeklarowania obiektu oraz to: 

- inicjalizacja na stosie ( czas życia -> do końca wykonania zakresu, w którym obiekt został zainicjalizowany )

- dynamiczna alokacja poprzez operator new ( czas życia -> do wywołania operatora delete; operator delete wywołuje destruktor oraz zwalnia pamięć używaną przez obiekt ) 

Oraz przypadek wcześniej wspomniany jako "nieklasyczny": 

- dynamiczna alokacja w określonym obszarze pamięci poprzez operator placement new ( czas życia ->  do wywołania destruktora; wywołanie destruktora nie zwalnia pamięci. zwolnienie pamięci nie wywołuje destruktora )

Prawie nigdy nie chcesz korzystać z trzeciej metody. Wyjątkiem są mikrokontrolery, programy wywoływane w warunkach ściśle ograniczonych zasobów pamięciowych, komunikacja bezpośrednio z hardware'm... generalnie hardcore dla niedzielnego programisty. 

 

Przypadek "nieklasyczny" jest jednak o tyle ciekawy, że pozwala łatwo zobaczyć, co dokładnie dzieje się w pozostałych dwóch przypadkach, gdzie rezerwacja i zwalnianie pamięci jest załatwiane automatcznie. W przypadku placement new to programista jest odpowiedzialny za... wszystko. 

Poprawny sposób stworzenie i destrukcji takiego obiektu jest, jak następuje: 





char* buffer = new char[ sizeof( mojaKlasa ) ]; // 0. Rezerwacja pamięci o rozmiarze potrzebnym dla naszej klasy. Korzystamy z typu char, gdyż ma on to do siebie, iż zawsze zajmuje 1 bajt, dzięki czemu... bardzo dobrze reprezentuje 1 bajt 
mojaKlasa* wskaznik_na_obiekt = new ( buffer ) mojaKlasa(); // 1. Konstrukcja obiektu poprzez operator placement new. W tym momencie zostanie wywołany konstruktor mojaKlasa::mojaKlasa()
assert( wskaznik_na_obiekt == buffer ); // test, czy wskaźnik na obiekt jest tym samym, co wskaźnik na bufor, w którym ten obiekt powinien się znajdować. Jeśli ten test zawiedzie, coś jest POWAŻNIE NIE W PORZĄDKU [ nigdy nie powinien zawieść ] 
wskaznik_na_obiekt->~mojaKlasa(); // 2. Destrukcja obiektu. Manualnie wywołujemy destruktor. 
delete buffer; // 3. Zwolnienie wcześniej zadeklarowanej pamięci

Dla porównania, wszystko to jest automatycznie przedsiębrane w momencie "klasycznego" tworzenia obiektów: 





mojaKlasa* wskaznik_na_obiekt = new mojaKlasa(); // krok 0. i 1. jednoczęśnie
delete wskaznik_na_obiekt; // krok 2. i 3. jednocześnie

Oraz poprzez inicjalizacją na stosie: 





{ 
  mojaKlasa obiekt(); // nie-do-końca krok 0. (pamięć jest rezerowowana w zupełnie innym, lokalnym, "zbiorniku") razem z nie-do-końca krokiem 1 (obiekt jest tworzony w zupełnie innym, lokalnym, "zbiorniku").
  return; // jako, że obiekt jest tworzony lokalnie (na stosie) - jest on automatycznie niszczony, gdy wyjdziemy poza zakres jego życia. Zazwyczaj jest to blok funkcji w której został tworzony albo czas życia obiektu, do którego należy.
}

I tak od najcięższego przypadku doszliśmy do najbardziej trywialnego. Miejmy nadzieję, że to, co dzieje się "za sceną" podczas tworzenia i niszczenia obiektu jest już teraz odrobinę jaśniejsze.

 

 

 

@Sopel

Mimo, że nie jest to stricte zła odpowiedź, nie jest też sama w sobie dobra. IMHO. 

Ta sygnatura jest pusta.

Opublikowano

nie wiem dlaczego ale spoilery nie działają..

 

Co do Twojego pytania to już odpowiadam, kiedy zadałem to pytanie w szkole nauczycielka natychmiast zmieniła temat, a kiedy zapytałem ponownie nie odpowiedziała nic.

 

Taka sama sytuacja miała miejsce przy strukturach, tyle że w tym przypadku udało mi się wygooglować odpowiedź i teraz już myślę że wiem do czego służy struktura, tworzy po prostu nowy "typ zmiennych"

Obama wie, co robisz!!!
131894.jpg                                                                                                                                                    4906167742.png

                                                                                                                                                                                                                                                                                      LTE Play Opole

Opublikowano

"nie wiem dlaczego ale spoilery nie działają.."

Well, that's disappointing... Zmieniłem formatowanie odrobinę, więc spróbuj teraz, a wersja surowa: https://dl.dropboxusercontent.com/u/14088568/mpcpost%20(1).txt

 

W C++ nie ma żadnej różnicy między klasą a strukturą poza domyślną widocznością ( publiczna dla struktur, prywatna dla klas ).

Słowo kluczowe struct zostało w C++ wyłącznie ze względu na kompatybilność wsteczną z C. 

 

I obie te rzeczy robią znacznie więcej, niż tylko tworzą "nowy typ" :)

 

 

@EDIT

W zasadzie należy napomknąć, że względy estetyczne i dobrego wychowania sugerują, by używać struktur w przypadku klas typu POD ( Plain Old Data ). Do dalszego własnego researchu. 

Ta sygnatura jest pusta.

Zarchiwizowany

Ten temat przebywa obecnie w archiwum. Dodawanie nowych odpowiedzi zostało zablokowane.

×
×
  • Dodaj nową pozycję...